#include <stddef.h>
#include <round.h>
#include "./vm/swap.h"
#include "./vm/frame.h"
#include "./userprog/pagedir.h"
#include "./threads/thread.h"
#include "./threads/vaddr.h"
#include "./lib/kernel/bitmap.h"
#ifdef _TEST_
#include "./lib/stdio.h"
	extern size_t get_frame_index(void*);
#endif
const int sects_per_page = DIV_ROUND_UP(PGSIZE, DISK_SECTOR_SIZE);

typedef uint32_t swap_slot_t;

static uint32_t swap_size;
static struct lock swap_lock;
static struct disk* swap_disk = NULL;
/* If bitmap if flase then the sector indicating that bit is free */
static struct bitmap* swap_pool = NULL;

bool swap_out_slot(swap_slot_t* slot);
static inline disk_sector_t slot_to_sector(swap_slot_t slot);
static inline swap_slot_t sector_to_slot(disk_sector_t sector);
static bool is_writable(struct page*);

static bool is_writable(struct page* swap_page)
{
	return pagedir_iswritable_page (swap_page->owner->pagedir, swap_page->uaddr);
}

void swap_init(void)
{
	/*swap_init must excute one time*/
	ASSERT(swap_disk == NULL);
	swap_disk = disk_get(1, 1);
	swap_size = disk_size(swap_disk)/sects_per_page;
	swap_pool = bitmap_create(swap_size);
	lock_init(&swap_lock);
}
bool swap_out_slot(swap_slot_t* slot)
{
	size_t p = bitmap_scan_and_flip(swap_pool, 0, 1, false);
	/* there is not available slot*/
	if(p == BITMAP_ERROR)
	{
		return false;
	}
	*slot = p;
	return true;
}
inline disk_sector_t slot_to_sector(swap_slot_t slot)
{
	return slot*sects_per_page;
}
inline swap_slot_t sector_to_slot(disk_sector_t sector)
{
	return sector/sects_per_page;
}
bool swap_out(struct page* swap_page)
{
	lock_acquire(&swap_lock);

	swap_slot_t slot;
	disk_sector_t sector;
	int i;
	/* If all slot is full */
	if(!swap_out_slot(&slot))
	{
		lock_release(&swap_lock);
		return false;
	}
	sector = slot_to_sector(slot);
	swap_page->swap_dsk_idx = sector;
	void* kaddr = get_kpage(swap_page);

	for(i = 0; i < sects_per_page ; i++)
		disk_write(swap_disk, sector + i, (uint8_t*)kaddr + i*DISK_SECTOR_SIZE);

#ifdef _TEST_
		printf("Swap out : upage %p, frameidx %d, swapslot %d\n", swap_page->uaddr, get_frame_index(kaddr), slot);
#endif
	lock_release(&swap_lock);

	return true;
}
bool swap_in(struct page* swap_page)
{
	int i;
	disk_sector_t sector = swap_page->swap_dsk_idx;
	swap_slot_t slot = sector_to_slot(sector);
	bool writable = is_writable(swap_page);
	void *kpage = get_frame(false);
	if(kpage == NULL || !frametable_set_frame(swap_page->uaddr, kpage, writable))
	{
		return false;
	}
	lock_acquire(&swap_lock);
	bitmap_set(swap_pool, slot, false);
	for(i = 0; i < sects_per_page ; i++)
		disk_read(swap_disk, sector + i, (uint8_t*)kpage + i*DISK_SECTOR_SIZE);
	swap_page->swap_dsk_idx = -1;
#ifdef _TEST_
		printf("Swap in : upage %p, frameidx %d, swapslot %d\n", swap_page->uaddr, get_frame_index(kpage), slot);
#endif
	lock_release(&swap_lock);
	return true;
}

void swap_clear(struct page* swap_page)
{
	lock_acquire(&swap_lock);
	bitmap_set(swap_pool, sector_to_slot(swap_page->swap_dsk_idx), false);
	swap_page->swap_dsk_idx = -1;
	lock_release(&swap_lock);
}
